﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Runtime.InteropServices;
using System.ServiceModel;
using System.ServiceModel.Description;
using System.Text;
using System.Xml.Linq;
using SKYPE4COMLib;
using System.Threading;
using System.Security.Cryptography;
using Skype.Client.Core;

namespace Skype.Client.Data {
	public class ExchangeService : IExchange {
		#region Fields
		private readonly ISkype skype;
		
		private ServiceHost proxy;
		private const SaveOptions XmlOptions = SaveOptions.DisableFormatting;
		private static readonly IEqualityComparer<XElement> UserEqualityComparer = new SkypeContactEquality();
		//GET USER anappo2 AVATAR 1 c:\stuff\userpic.jpg
		private readonly String AvatarCacheDirectory;
		private static readonly List<String> Users = new List<String>();
		private static readonly Dictionary<String, Byte[]> Avatars = new Dictionary<String, Byte[]>();
		private static readonly MD5 md5 = MD5.Create();
		private static readonly ManualResetEvent AvatarWait = new ManualResetEvent(false);

		private String avatarActiveUser;
		private String avatarActiveGuid;
		#endregion
		#region Nested
		private class SkypeContactEquality : IEqualityComparer<XElement> {
			public bool Equals(XElement x, XElement y) {
				if (Object.ReferenceEquals(x, y)) {
					return true;
				}
				if (x == null || y == null){
					return false;
				}
				String usernameX = x.GetAttributeValue("Username");
				String usernameY = y.GetAttributeValue("Username");

				if (String.IsNullOrEmpty(usernameX) || String.IsNullOrEmpty(usernameY)) {
					return false;
				}
				return String.Equals(usernameX, usernameY);
			}
			public int GetHashCode(XElement obj) {
				if (obj != null) {
					return 0;
				}

				string username = obj.GetAttributeValue("Username");
				if (String.IsNullOrEmpty(username)) {
					return 0;
				}

				return username.GetHashCode();
			}
		}
		#endregion
		#region Constructor
		public ExchangeService() {
			skype = CreateSkype();
			try {
				skype.Attach();
			} catch (COMException exc) {
				if (!String.IsNullOrEmpty(exc.Message) && exc.Message == "Wait timeout.") {
					//No skype online, check if that's true.

					var skypes = from pr in Process.GetProcesses()
								 where pr.ProcessName == "skype.exe"
								 select pr;

					if (skypes.Count() > 0) {
						OnError("Dead men don't bite. {0}", exc.Message);
					} else {
						OnError("Arrr, you need skype runnin' on this, or other pc, in order t' use Skyper application on your phone. Gar, Where can I find a bottle o'rum?");
					}
				} else {
					
				}
			} catch (Exception exc) {
				System.Windows.Forms.MessageBox.Show("There is something wrong with yar skype.");
			}
			AvatarCacheDirectory = CreateAvatarCacheDirectory();
			ThreadPool.QueueUserWorkItem(LoadDataAsync);
		}
		#endregion
		#region Private
		private ISkype CreateSkype() {
			ISkype skype = new SKYPE4COMLib.Skype();

			_ISkypeEvents_Event events = (_ISkypeEvents_Event)skype;

			events.Error += new _ISkypeEvents_ErrorEventHandler(Skype_Error);
			events.Reply += new _ISkypeEvents_ReplyEventHandler(Skype_Reply);
			events.Command += new _ISkypeEvents_CommandEventHandler(Skype_Command);
			return skype;
		}
		private void CheckMessage(String cmd) {
			if (String.IsNullOrEmpty(cmd)) {
				return;
			}
			//Console.WriteLine("[#] {0}", cmd);

			//[#] USER <USRHANDLE> ONLINESTATUS ONLINE
			//[#] USER <USRHANDLE> ONLINESTATUS OFFLINE
			//[#] USER <USRHANDLE> ONLINESTATUS AWAY
			//[#] USER <USRHANDLE> ONLINESTATUS OFFLINE
		}
		private void Skype_Reply(Command pCommand) {
			//"USER wasntm3 AVATAR 1 C:\\Users\\Jefis\\Documents\\Visual Studio 2010\\Projects\\Skyper\\Skype\\Client\\bin\\Debug\\Cache\\e54973a9-c9e1-08a2-af2b-67bd9e3304a5"
			if (pCommand != null && pCommand.Reply.StartsWith(String.Format("USER {0} AVATAR 1", this.avatarActiveUser))) {
				AvatarWait.Set();
			}

			if (pCommand == null) {
				return;
			}

			//String reply = pCommand.Reply;
			//this.CheckMessage(reply);
		}
		private void Skype_Command(Command pCommand) {
			if (pCommand == null) {
				return;
			}

			//String reply = pCommand.Reply;
			//this.CheckMessage(reply);
		}
		private void Skype_Error(Command pCommand, int Number, string Description) {
			if (Number == 122) {
				//Avatar error?
				AvatarWait.Set();
			}
		}
		private void LoadDataAsync(Object state) {
			this.LoadUsers();
			this.LoadAvatars();
		}
		private void LoadUsers() {
			foreach (IGroup grp in this.skype.Groups) {
				foreach (IUser usr in grp.Users) {
					String username = usr.Handle;

					if (!Users.Contains(username)) {
						Users.Add(username);
					}
				}
			}
		}
		private void LoadAvatars() {
			
			foreach (String username in Users) {
				try {
					Guid hash = this.EncodeUsername(username);
					DateTime last = DateTime.MinValue;
					FileInfo inf = new FileInfo(AvatarCacheDirectory + hash.ToString());
					if (inf.Exists && (DateTime.Now - inf.LastWriteTime) < TimeSpan.FromDays(2)) {
						if (inf.Length > 0) {
							Byte[] data = File.ReadAllBytes(inf.FullName);

							Avatars[username] = data;
						}
					} else {
						using (var obj = inf.Create()) {

						}
						this.avatarActiveGuid = hash.ToString();
						this.avatarActiveUser = username; 

						AvatarWait.Reset();
						Command cmd = skype.get_Command(0, String.Format("GET USER {0} AVATAR 1 {1}",username,inf.FullName));
						this.skype.SendCommand(cmd);

						if (AvatarWait.WaitOne(TimeSpan.FromSeconds(15))) {
							//Ok!
							Byte[] data = File.ReadAllBytes(inf.FullName);

							if (data.Length > 0) {
								Avatars[username] = data;
							}
						}
					}
				} catch {

				}
			}
		}

		private String CreateAvatarCacheDirectory() {
			String curret = Directory.GetCurrentDirectory();
			String path = curret + "\\Cache\\";
			if (Directory.Exists(path)) {
				return path;
			} else {
				try {
					DirectoryInfo info = Directory.CreateDirectory(path);

					if (info.Exists) {
						return path;
					}
				} catch {

				}
			}
			return Path.GetTempPath() + Guid.NewGuid() + "\\";
		}
		private void OnError(String text) {
			System.Windows.Forms.MessageBox.Show(text, "Error", System.Windows.Forms.MessageBoxButtons.OK, System.Windows.Forms.MessageBoxIcon.Error);
		}
		private void OnError(String format, params object[] obj) {
			OnError(String.Format(format, obj));
		}
		private XElement Convert(IUser user) {
			XElement contact = new XElement("Contact");
			contact.SetAttributeValue("Username", user.Handle);
			contact.SetAttributeValue("Status", (int)user.OnlineStatus);

			if (!String.IsNullOrEmpty(user.FullName)) {
				contact.SetAttributeValue("Fullname", user.FullName);
			}

			if (!String.IsNullOrEmpty(user.DisplayName)) {
				contact.SetAttributeValue("Displayname", user.DisplayName);
			}

			if (!String.IsNullOrEmpty(user.Country)) {
				contact.SetAttributeValue("Country", user.Country);
			}

			if (!String.IsNullOrEmpty(user.City)) {
				contact.SetAttributeValue("City", user.City);
			}

			if (!String.IsNullOrEmpty(user.Homepage)) {
				contact.SetAttributeValue("Homepage", user.Homepage);
			}

			if (!String.IsNullOrEmpty(user.MoodText)) {
				contact.SetAttributeValue("Mood", user.MoodText);
			}

			if (!String.IsNullOrEmpty(user.PhoneHome)) {
				contact.SetAttributeValue("PhoneHome", user.PhoneHome);
			}

			if (!String.IsNullOrEmpty(user.PhoneOffice)) {
				contact.SetAttributeValue("PhoneOffice", user.PhoneOffice);
			}

			if (!String.IsNullOrEmpty(user.PhoneMobile)) {
				contact.SetAttributeValue("PhoneMobile", user.PhoneMobile);
			}
			return contact;
		}
		private XElement Convert(IGroup group) {
			XElement grp = new XElement("Group");
			grp.SetAttributeValue("Displayname", group.DisplayName);
			grp.SetAttributeValue("ID", group.Id);

			foreach (IUser usr in group.Users) {
				XElement contact = new XElement("Contact");
				contact.SetAttributeValue("Username", usr.Handle);

				grp.Add(contact);
			}

			return grp;
		}
		private XElement Convert(IChatMessage message) {
			XElement msg = new XElement("Message");
			String body = message.Body;

			if (String.IsNullOrEmpty(body)) {
				return null;
			}


			String usr = message.FromHandle;
			DateTime time = message.Timestamp;
			int id = message.Id;
			if (id == 0) {
				return null;
			}
			if (DateTime.Now - time < TimeSpan.FromDays(5)) {


				msg.SetAttributeValue("Time", time);
				msg.SetAttributeValue("ID", id);
				msg.SetAttributeValue("Owner", (usr == skype.CurrentUserHandle));
				msg.SetAttributeValue("Text", System.Convert.ToBase64String(Encoding.UTF8.GetBytes(body)));

				return msg;
			}

			return null;
		}

		private IEnumerable<XElement> GetUsers() {
			var list = new List<XElement>();
			try {
				foreach (IGroup grp in this.skype.Groups) {
					foreach (IUser usr in grp.Users) {
						XElement contact = Convert(usr);
						if (!list.Contains(contact, UserEqualityComparer)) {
							list.Add(contact);
						}
					}
				}
			} catch {
#if DEBUG
				if (Debugger.IsAttached) {
					Debugger.Break();
				}
#endif
			}
			return list;
		}
		private IEnumerable<XElement> GetMessages(Object state,String username, DateTime offset) {
		
			List<XElement> yield = new List<XElement>();
			

			try {
				ChatMessageCollection collection = null;
				String me = this.skype.CurrentUserHandle;
				//Search for messages.
				//1. Active chats
				foreach (IChat chat in this.skype.RecentChats) {
					if (chat.Members.Count == 2 && chat.DialogPartner == username) {
						collection = chat.RecentMessages;
					}
				}
				if (collection != null && collection.Count > 0) {
					for (int i = 1; i < collection.Count; i++) {
						IChatMessage message = collection[i];
						if (message.Timestamp > offset) {
							XElement msg = Convert(message);
							if (msg != null) {
								yield.Add(msg);
							}
						}
					}
				}
			} catch {
#if DEBUG
				if (Debugger.IsAttached) {
					Debugger.Break();
				}
#endif
			}
			return yield;
		}
		private Guid EncodeUsername(String username) {
			return new Guid(md5.ComputeHash(Encoding.UTF8.GetBytes(username)));
		}
		private Boolean Available(TUserStatus status){
			return (status != TUserStatus.cusLoggedOut && status != TUserStatus.cusOffline && status != TUserStatus.cusUnknown);
		}

		private Byte[] Compress(String value) {
			return Encoding.UTF8.GetBytes(value);
		}
		#endregion
		#region Public
		public bool Start(Uri uri) {
			try {
				proxy = new ServiceHost(typeof(ExchangeService), uri);

				// Enable metadata publishing.
				ServiceMetadataBehavior smb = new ServiceMetadataBehavior();
				smb.HttpGetEnabled = true;
				smb.MetadataExporter.PolicyVersion = PolicyVersion.Policy15;
				proxy.Description.Behaviors.Add(smb);
				proxy.Open();

				return true;
			} catch (Exception exc) {
				OnError(exc.Message);
			} 
			return false;
		}
		public void Stop() {
			try {
				this.proxy.Close();

			} catch {

			}
		}

		public byte[] Login() {
			throw new NotImplementedException();
		}
		public byte[] GetContacts() {
			Byte[] response = null;
			try {
				var list = GetUsers();
				var output = from n in list
							 where Available((TUserStatus)int.Parse(n.GetAttributeValue("Status")))
							 select n;

				XElement node = new XElement("Contacts");
				foreach (var c in output) {
					node.Add(c);
				}

				String value = node.ToString(XmlOptions);
				response = Compress(value);


			} catch {

			}
			return response;
		}
		public byte[] GetRecent() {
			Byte[] response = null;

			try {
				var list = new List<XElement>();

				int max = this.skype.RecentChats.Count;
				for (int i = 0; (i < max || list.Count < 8); i++) {
					IChat chat = this.skype.RecentChats[i];
					if (chat.Members.Count > 2) {
						continue;
					}
					String part = chat.DialogPartner;
					DateTime time = chat.Timestamp;
					DateTime activity = chat.ActivityTimestamp;

					XElement recentChat = new XElement("Chat");
					recentChat.SetAttributeValue("Created", time);
					recentChat.SetAttributeValue("Updated", activity);
					recentChat.SetAttributeValue("Partner", part);

					for (int n = 0; n < chat.RecentMessages.Count; n++) {
						XElement msg = Convert(chat.RecentMessages[i]);

						recentChat.Add(msg);
					}

					list.Add(recentChat);
				}
			} catch {

			}
			return response;
		}
		public byte[] GetGroups() {
			Byte[] response = null;
			try {
				XElement node = new XElement("Groups");
				foreach (IGroup grp in this.skype.Groups) {
					XElement child = Convert(grp);

					node.Add(child);
				}
				String value = node.ToString(XmlOptions);
				response = Compress(value);
			} catch {

			}
			return response;
		}
		public byte[] GetMyself() {
			Byte[] response = null;
			try {
				var node = Convert(skype.User);

				string value = node.ToString(XmlOptions);

				return Compress(value);
			} catch {

			}
			return response;
		}
		public byte[] GetAvatars() {
			XElement root = new XElement("Avatars");
			foreach (var pair in Avatars) {
				String value = System.Convert.ToBase64String(pair.Value);

				XElement avatar = new XElement("Avatar");
				avatar.SetAttributeValue("Username", pair.Key);
				avatar.Value = value;
				root.Add(avatar);
			}
			String output = root.ToString();
			Byte[] data = Encoding.UTF8.GetBytes(output);
			Byte[] zip = Zipper.Zip(data);

			return zip;
		}
		public byte[] GetMessages(string username, DateTime offset) {
			Byte[] response = null;

			try {
				var list = this.GetMessages(null, username, offset);
				XElement messages = new XElement("Messages");
				foreach (var m in list) {
					messages.Add(m);
				}

				String value = messages.ToString(XmlOptions);
				response = Zipper.Zip(Compress(value));
			} catch {

			}
			return response;
		}
		#endregion
	}
}
